/**
 * $Id: Handler.java 23 2006-09-17 20:02:05Z willi.w $
 */
package at.wwu.tunes2db.xml;

import java.sql.SQLException;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.xml.sax.*;

import at.wwu.tunes2db.export.Destination;
import at.wwu.tunes2db.utils.Formatter;
import at.wwu.tunes2db.types.*;

/**
 * @author willi.w
 * Methods to be called while parsing the .xml file
 */
public class LibraryContentHandler implements ContentHandler {

    private static final Logger log = Logger.getLogger(LibraryContentHandler.class.getName());

    public enum PosEnum {
        rootLevel, tracksLevel, trackLevel;
    }

    public enum ElemEnum {
        otherElem, keyElem, dictElem, integerElem, stringElem, dateElem, boolElem;
    }

    public enum InfoEnum {
        other, trackId, name, artist, album, genre, kind, size, total_time, disc_number, disc_count, track_number,
        track_count, year, date_modified, date_added, bit_rate, sample_rate, play_count, play_date, play_date_utc,
        normalization, persistent_id, track_type, location, file_folder_count, library_folder_count;
    }

    DateFormat iDateformat = DateFormat.getDateTimeInstance();

    public PosEnum position = null;

    public ElemEnum elem = null;

    public ElemEnum lastElem = null;

    public InfoEnum infofield = null;

    public int tracks;

    public int genres;

    public int kinds;

    public int types;

    public int albums;

    public int artists;

    public int trackNum;

    public int size;

    private int disc_count = 0;

    private int disc_number = 0;

    private int track_count = 0;

    private int track_number = 0;

    private int year = 0;

    private Date date_modified = null;

    private Date date_added = null;

    private int bit_rate = 0;

    private int sample_rate = 0;

    private int play_count = 0;

    private int play_date = 0;

    private Date play_date_utc = null;

    private int normalization = 0;

    private String persistent_id = null;

    private Type type_id = null;

    private String location = null;

    private int file_folder_count = 0;

    private int library_folder_count = 0;

    public int totalTime;

    public Track currentTrack = null;

    public Artist currentArtist = null;

    public Genre currentGenre = null;

    public Kind currentKind = null;

    public Type currentType = null;

    public Album currentAlbum = null;

    public StringBuffer currentSB = new StringBuffer();
    
    public String currentString = null;

    long start = 0;

    //public Destination dest;

    public Vector<Destination> destinations = new Vector<Destination>();

    public void addDestination(Destination new_dest) {
        destinations.add(new_dest);
    }

    public void characters(char[] ch, int start, int length) throws SAXException {
        switch (elem) {
        case keyElem:
        case stringElem:
        case integerElem:
            currentSB.append(ch, start, length);
            if (tracks%1000==0 && elem.equals(ElemEnum.keyElem)) {
                log.debug(String.valueOf(ch, start, length));
            }
            break;
        default:
            break;
        }
    }

    public void endDocument() throws SAXException {
        log.info(tracks + " tracks, " + artists + " artists, " + albums + " albums, " + genres + " genres");
        log.info("Parsing done in " + Formatter.formatMilliseconds(System.currentTimeMillis() - start));
        for (Destination dest : destinations) {
            dest.close();
        }
    }

    public void endElement(String uri, String localName, String qName) throws SAXException {
        //log.info(this.currentString.toString());
        currentString = currentSB.toString();
        switch (position) {
        case trackLevel:
            switch (elem) {
            case dictElem:
                for (Destination dest : destinations) {
                    try {
                        dest.addTrack(currentTrack);
                    } catch (Exception e) {
                        log.error(currentTrack + ": " + e.getMessage());
                    }
                }
                tracks++;
                if (tracks % 5000 == 0) {
                    log.info(tracks + " Tracks in "
                            + Formatter.formatMilliseconds(System.currentTimeMillis() - start));
                }
                currentTrack = null; // TODO: memsaver
                position = PosEnum.tracksLevel;
                break;
            default:
                // close (=write) trackelem
                switch (elem) {
                case keyElem:
                    if (currentString.equals("Track ID")) {
                        infofield = InfoEnum.trackId;
                    } else if (currentString.equals("Name")) {
                        infofield = InfoEnum.name;
                    } else if (currentString.equals("Artist")) {
                        infofield = InfoEnum.artist;
                    } else if (currentString.equals("Album")) {
                        infofield = InfoEnum.album;
                    } else if (currentString.equals("Genre")) {
                        infofield = InfoEnum.genre;
                    } else if (currentString.equals("Kind")) {
                        infofield = InfoEnum.kind;
                    } else if (currentString.equals("Size")) {
                        infofield = InfoEnum.size;
                    } else if (currentString.equals("Total Time")) {
                        infofield = InfoEnum.total_time;
                    } else if (currentString.equals("Disc Number")) {
                        infofield = InfoEnum.disc_number;
                    } else if (currentString.equals("Disc Count")) {
                        infofield = InfoEnum.disc_count;
                    } else if (currentString.equals("Track Number")) {
                        infofield = InfoEnum.track_number;
                    } else if (currentString.equals("Track Count")) {
                        infofield = InfoEnum.track_count;
                    } else if (currentString.equals("Year")) {
                        infofield = InfoEnum.year;
                    } else if (currentString.equals("Date Modified")) {
                        infofield = InfoEnum.date_modified;
                    } else if (currentString.equals("Date Added")) {
                        infofield = InfoEnum.date_added;
                    } else if (currentString.equals("Bit Rate")) {
                        infofield = InfoEnum.bit_rate;
                    } else if (currentString.equals("Sample Rate")) {
                        infofield = InfoEnum.sample_rate;
                    } else if (currentString.equals("Play Count")) {
                        infofield = InfoEnum.play_count;
                    } else if (currentString.equals("Play Date")) {
                        infofield = InfoEnum.play_date;
                    } else if (currentString.equals("Play Date UTC")) {
                        infofield = InfoEnum.play_date_utc;
                    } else if (currentString.equals("Normalization")) {
                        infofield = InfoEnum.normalization;
                    } else if (currentString.equals("Persistent ID")) {
                        infofield = InfoEnum.persistent_id;
                    } else if (currentString.equals("Track Type")) {
                        infofield = InfoEnum.track_type;
                    } else if (currentString.equals("Location")) {
                        infofield = InfoEnum.location;
                    } else if (currentString.equals("File Folder Count")) {
                        infofield = InfoEnum.file_folder_count;
                    } else if (currentString.equals("Library Folder Count")) {
                        infofield = InfoEnum.library_folder_count;
                    } else {
                        infofield = InfoEnum.other;
                    }
                    break;
                case stringElem:
                    switch (infofield) {
                    case name:
                        currentTrack.setName(currentString);
                        break;
                    case artist:
                        currentArtist = Artist.add(currentString);
                        try {
                            for (Destination dest : destinations) {
                                dest.addArtist(currentArtist);
                            }
                            if (currentArtist.isNew()) {
                                artists++;
                            }
                        } catch (SQLException e) {
                            throw new SAXException(e);

                        }
                        currentTrack.setArtist(currentArtist);
                        break;
                    case album:
                        currentAlbum = Album.add(currentString);
                        assert currentAlbum != null;

                        try {
                            for (Destination dest : destinations) {
                                dest.addAlbum(currentAlbum);
                            }
                            if (currentAlbum.isNew()) {
                                albums++;
                            }
                        } catch (SQLException e) {
                            throw new SAXException(e);
                        }

                        currentTrack.setAlbum(currentAlbum);
                        break;
                    case genre:
                        currentGenre = Genre.add(currentString);
                        assert currentGenre != null;

                        try {
                            for (Destination dest : destinations) {
                                dest.addGenre(currentGenre);
                            }
                            if (currentGenre.isNew()) {
                                genres++;
                            }
                        } catch (SQLException e) {
                            throw new SAXException(e);
                        }

                        currentTrack.setGenre(currentGenre);
                        break;
                    case kind:
                        currentKind = Kind.add(currentString);
                        assert currentKind != null;

                        try {
                            for (Destination dest : destinations) {
                                dest.addKind(currentKind);
                            }
                            if (currentKind.isNew()) {
                                kinds++;
                            }
                        } catch (SQLException e) {
                            throw new SAXException(e);
                        }
                        currentTrack.setKind_id(currentKind);
                        break;
                    case track_type:
                        currentType = Type.add(currentString);
                        assert currentType != null;

                        try {
                            for (Destination dest : destinations) {
                                dest.addType(currentType);
                            }
                            if (currentType.isNew()) {
                                types++;
                            }
                        } catch (SQLException e) {
                            throw new SAXException(e);
                        }
                        currentTrack.setType_id(currentType);
                        break;
                    }
                    break;
                case integerElem:
                    switch (infofield) {
                    case trackId:
                        trackNum = Integer.parseInt(currentString);
                        currentTrack.setItunes_id(trackNum);
                        break;
                    case total_time:
                        try {
                            totalTime = Integer.parseInt(currentString);
                        } catch (NumberFormatException e) {
                            totalTime = 0;
                        }
                        currentTrack.setTotal_time(totalTime);
                        break;
                    case size:
                        try {
                            size = Integer.parseInt(currentString);
                        } catch (NumberFormatException e) {
                            size = 0;
                        }
                        currentTrack.setSize(size);
                        break;
                    case disc_count:
                        try {
                            disc_count = Integer.parseInt(currentString);
                        } catch (NumberFormatException e) {
                            disc_count = 0;
                        }
                        currentTrack.setDisc_count(disc_count);
                        break;
                    case disc_number:
                        try {
                            disc_number = Integer.parseInt(currentString);
                        } catch (NumberFormatException e) {
                            disc_number = 0;
                        }
                        currentTrack.setDisc_number(disc_number);
                        break;
                    case track_count:
                        try {
                            track_count = Integer.parseInt(currentString);
                        } catch (NumberFormatException e) {
                            track_count = 0;
                        }
                        currentTrack.setTrack_count(track_count);
                        break;
                    case track_number:
                        try {
                            track_number = Integer.parseInt(currentString);
                        } catch (NumberFormatException e) {
                            track_number = 0;
                        }
                        currentTrack.setTrack_number(track_number);
                        break;
                    case year:
                        try {
                            year = Integer.parseInt(currentString);
                        } catch (NumberFormatException e) {
                            year = 0;
                        }
                        currentTrack.setYear(year);
                        break;
                    }
                    break;
                case dateElem:
                    switch (infofield) {
                    case date_modified:
                        try {
                            date_modified = iDateformat.parse(currentString);
                        } catch (ParseException e) {
                            date_modified = null;
                        }
                        currentTrack.setDate_modified(date_modified);
                        break;
                    case date_added:
                        try {
                            date_added = iDateformat.parse(currentString);
                        } catch (ParseException e) {
                            date_added = null;
                        }
                        currentTrack.setDate_added(date_added);
                        break;
                    }
                    break;
                }
                break;
            }
            break;
        case tracksLevel:
            switch (elem) {
            case dictElem:
                position = PosEnum.rootLevel;
                break;
            default:
                switch (elem) {
                case keyElem:
                    position = PosEnum.trackLevel;
                    trackNum = Integer.parseInt(currentString);
                    currentTrack = new Track();
                    break;
                }
                break;
            }
            break;
        case rootLevel:
            switch (elem) {
            case keyElem:
                if (currentString.equals("Tracks")) {
                    position = PosEnum.tracksLevel;
                }
                break;
            }
            break;
        }
        elem = lastElem;
        currentSB.delete(0, currentSB.length());
        currentString = null;
    }

    public void endPrefixMapping(String prefix) throws SAXException {
        // TODO Auto-generated method stub

    }

    public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
        // TODO Auto-generated method stub

    }

    public void processingInstruction(String target, String data) throws SAXException {
        // TODO Auto-generated method stub

    }

    public void setDocumentLocator(Locator locator) {
        // TODO Auto-generated method stub

    }

    public void skippedEntity(String name) throws SAXException {
        // TODO Auto-generated method stub

    }

    public void startDocument() throws SAXException {
        start = System.currentTimeMillis();
        position = PosEnum.rootLevel;
        elem = ElemEnum.otherElem;
        lastElem = ElemEnum.otherElem;
        try {
            for (Destination dest : destinations) {
                dest.open();
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
            throw new SAXException(e);
        }
    }

    public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {

        lastElem = elem;
        if (qName.equals("key")) {
            elem = ElemEnum.keyElem;
        } else if (qName.equals("dict")) {
            elem = ElemEnum.dictElem;
        } else if (qName.equals("integer")) {
            elem = ElemEnum.integerElem;
        } else if (qName.equals("string")) {
            elem = ElemEnum.stringElem;
        } else if (qName.equals("date")) {
            elem = ElemEnum.dateElem;
        } else if (qName.equals("true") || qName.equals("false")) {
            elem = ElemEnum.boolElem;
        } else {

            elem = ElemEnum.otherElem;
        }
    }

    public void startPrefixMapping(String prefix, String uri) throws SAXException {
        // TODO Auto-generated method stub

    }

    public int getAlbums() {
        return albums;
    }

    public int getArtists() {
        return artists;
    }

    public int getGenres() {
        return genres;
    }

    public int getTracks() {
        return tracks;
    }

}
